Table of Contents

1.Обзор системы | 2.Мой первый проект | 3.Модель | 4.Представление | 5.Табличное представление | 6.Объектно-реляционное связывание | 7.Контроллеры | 8.Приложение | 9.Расширенные возможности

Глава 4: Представление

Как вы, наверное, уже поняли, каркас OpenXava создает пользовательский интерфейс из модели. Во многих случаях интерфейс, создаваемый полностью автоматически, сразу может быть использован конечным пользователем и не требует дополнительной настройки. Тем не менее, OX предоставляет широкие возможности по настройке интерфейса. В этой главе мы расскажем, как изменять вид(представление) и поведение пользовательского интерфейса, формируемого OX.

Синтаксис @View

С помощью аннотации @View мы можем управлять форматом представления переменных-членов класса (свойств). Данная аннотация применяется к сущности (entity) или встраиваемому классу (embeddable class).
Синтаксис конструкции @View таков:
@View(
    name="name",        // 1
    members="members",  // 2
    extendsView="view"  // 3  New in v3.1.2
)
public class MyEntity {
  1. name (необязательный элемент): Имя идентифицирует представление, оно используется для настройки поведения OpenXava (например, в файле application.xml) или для ссылки на данное представление из другой сущности. Безымянное представление используется как представление по-умолчанию, т.е. является естественным способом представления объектов этого типа.
  2. members (необязательный элемент): Список членов представления. Определяет список свойств, отображаемых в данном представлении, а также определяет порядок их отображения. Если данный параметр не определен, то по-умолчанию представление показывает все свойства (исключая скрытые (hidden)) в том же порядке, как они определены в модели. Используя параметр members можно создавать секции или объединять свойства в группы, а также задавать web-ссылки на созданные вами "действия" (см. контроллеры).
  3. extendsView (необязательный элемент): (с версии in v3.1.2) Наследуемое представление. Все свойства, определенные в представлении, указанном в extendsView, автоматически добавляются в текущее представление (см. наследование представлений).
Можно создать несколько представлений одной модели (сущности). Для этого предназначена аннотация @Views .

Представление модели без @View (Стандартное представление).

По-умолчанию (если не использовать @View) все свойства модели отображаются в порядке, как они определены в модели, каждое свойство на отдельной строке.
Рассмотрим пример модели:
@Entity
@IdClass(ClerkKey.class)
public class Clerk {
 
    @Id @Required
    @Column(length=3, name="ZONE")
    private int zoneNumber;
 
    @Id @Required
    @Column(length=3, name="OFFICE")
    private int officeNumber;
 
    @Id  @Required
    @Column(length=3, name="NUMBER")
    private int number;
 
    @Required @Column(length=40)
    private String name;
 
    // Getters and setters
    ...
 
 }
Давайте посмотрим, как будет выглядеть форма, соответствующая модели, приведенной выше:
view_en010.jpg
Все поля в форме представлены в том же порядке, как они были определены в модели, и показаны все свойства сущности. Это и называется далее "Стандартным Представлением" (Default View).

Пример @View

Если вам нужно поменять порядок вывода свойств и скрыть некоторые поля, то самое время воспользоваться конструкцией @View:
@Entity
@IdClass(ClerkKey.class)
@View(members="zoneNumber; officeNumber; number")
public class Clerk {
В выше приведенном примере свойство name не будет отображено. Конструкция members аннотации @View позволяет определить список отображаемых свойств. Отображаться они будут в том порядке, в котором указаны.

Простейшая компоновка Свойств

С помощью members мы также можем управлять компоновкой свойств в форме:
@View(members=
    "zoneNumber, officeNumber, number;" +
    "name"
)
Достигается это указанием разных разделителей между именами свойств. В нашем примере (выше) имена свойств разделяются запятыми(,) или точками с запятой(;):
  • Если перед следующим свойством в качестве разделителя используется запятая, то это свойство (его визуальный элемент) размещается сразу после предыдущего свойства справа от него.
  • Если перед следующим свойством в качестве разделителя используется точка с запятой, то данное свойство размещается ниже, начиная с левой границы формы (как бы на "на следующей строке").
Пример экрана к выше приведенному определению @View:
view_en020.jpg

Группы (Рамки)

Мы можем собрать несколько свойств в группу (group или frame). Для этого необходимо указать имя группы и далее в квадратных скобках перечислить свойства, которые группируются.
Пример такого представления:
@View(members=
    "id [ zoneNumber, officeNumber, number ];" +
    "name"
)
Посмотрим на визуальное отображение выше упомянутого представления:
view_en030.jpg
Сгруппированные свойства отображены внутри рамки. Имя группы отображено в левом верхнем угле рамки. Свойство name отображено вне рамки. Точка с запятой до свойства name привела к переносу отображения свойства ниже рамки (при использовании запятой свойство было бы отображено справа от рамки).

Горизонтальная компоновка групп

В одном представлении можно использовать несколько групп:
@View(members=
    "general [" +
    "    number;" +
    "    type;" +
    "    name;" +
    "]" +
    "contact [" +
    "    telephone;" +
    "    email;" +
    "    website;" +
    "]"
)
Группы отображаются последовательно одна за другой (слева направо):
view_en035.jpg

Вертикальная компоновка Групп

Чтобы отобразить группы друг под другом использует точку с запятой так же, как если это были просто свойства:
@View(members=
    "general [" +
    "    number;" +
    "    type;" +
    "    name;" +
    "];" +
    "contact [" +
    "    telephone;" +
    "    email;" +
    "    website;" +
    "]"
)
Теперь форма выглядет так:
view_en040.jpg

Вложенные Группы

Можно создавать вложенные группы. Это приятная возможность, которая позволяет компоновать элементы интерфейса очень простым способом и очень гибко.
Приведем пример:
@View(members=
    "invoice;" +
    "deliveryData [" +
    "    type, number;" +
    "    date;" +
    "    description;" +
    "    shipment;" +
    "    transportData [" +
    "        distance; vehicle; transportMode; driverType;" +
    "    ]" +
    "    deliveryByData [" +
    "        deliveredBy;" +
    "        carrier;" +
    "        employee;" +
    "    ]" +
    "]"
)
Соответствующая экранная форма:
view_en050.jpg

Вертикальное выравнивание отображения Свойств в Группе

OX поддерживает вертикальное выравнивание отображения свойств в форме.
Для начала посмотрим, как выглядит представление без применения выравнивания:
@View(name="Amounts", members=
    "year, number;" +
    "amounts [" +
        "customerDiscount, customerTypeDiscount, yearDiscount;" +
        "amountsSum, vatPercentage, vat;" +
    "]"
)
Вышеприведенное представление отображается так:
view_en060.jpg
Страшноватенько, вам не кажется...:-). Намного лучше было бы, если бы свойства были выровнены по вертикали.
Это реализуется указанием [# вместо [ в определении группы:
@View(name="Amounts", members=
    "year, number;" +
    "amounts [#" +
        "customerDiscount, customerTypeDiscount, yearDiscount;" +
        "amountsSum, vatPercentage, vat;" +
    "]"
)
Форма будет выглядеть намного лучше:
view_en070.jpg
Это работает также для вкладок (секций) (смотрите ниже).

Вкладки (Секции)

OX позволяет создавать вкладки. В OX они называются секции (sections). Вкладка определяется следующим образом: указывается имя вкладки,а затем в фигурных скобках свойства, которые должны быть отображены на вкладке.
Давайте посмотрим реализацию вкладок на примере сущности Invoice:
@View(members=
    "year, number, date, paid;" +
    "comment;" +
    "customer { customer }" +
    "details { details }" +
    "amounts { amountsSum; vatPercentage; vat }" +
    "deliveries { deliveries }"
)
Соответствующая форма выглядит так:
view_en080.jpg
Пользователь может выбрать вкладку, кликнув на её название. В примере мы видим, как с помощью вкладок в представлении показывать не только простые свойства, но и ссылки (например, customer), коллекции (details и deliveries в нашем примере).

Вложение Вкладок

Можно создавать вложение вкладок. Например:
@View(name="NestedSections", members=
    "year, number, date;" +
    "customer { customer }" +
    "data {" +
    "    details { details }" +
    "    amounts {" +
    "        vat { vatPercentage; vat }" +
    "        amountsSum { amountsSum }" +
    "    }" +
    "}" +
    "deliveries { deliveries }"
)
В результате форма будет выглядеть так:
view_en090.jpg

Вертикальное выравнивание свойств во Вкладках

Так же, как и для групп, во вкладках можно использовать конструкцию с # для вертикального выравнивания свойств. Например:
@View(name="AlignedAmountsInSection", members=
    "year, number;" +
    "customer { customer }" +
    "details { details }" +
    "amounts {#" +
        "customerDiscount, customerTypeDiscount, yearDiscount;" +
        "amountsSum, vatPercentage, vat;" +
    "}"
)

Наследование Представления(Вида) (с версии v3.1.2)

При определении нового представления можно наследовать свойства и компоновку из уже существующего представления. Таким образом, мы избавляемся от необходимости дублировать код в нескольких местах, повышаем читаемость и простоту сопровождения кода.
Наследование выполняется с помощью конструкции extendsView. Рассмотрим на примере, начнем с простого представления, которое затем будем наследовать:
@View(name="VerySimple", members="name, sex"),
Результирующий UI будет:
view_en92.jpg
Теперь мы хотим создадим новое представление-наследник предыдущего:
@View(name="Simple", extendsView="VerySimple", members="mainLanguage")
На экране мы видим:
view_en094.jpg
Свойства представления VerySimple автоматически были включены в представление Simple. "Личные" свойства Simple добавлены в конце отображения представления.
В этом примере мы наследовали представления одной модели.
Если мы используем наследование JPA, то можем наследовать представления класса-предка. Например, имея базовый класс Programmer
@Entity
@View(name="WithSections",
    members =
        "name, sex;" +
        "mainLanguage;" +
        "experiences { experiences }"
)
public class Programmer {
мы можем использовать представление WithSections в классе-наследнике JavaProgrammer:
@Entity
@View(name="WithSections", extendsView="super.WithSections",
    members =
        "favouriteFramework;" +
        "frameworks { frameworks }"
)
public class JavaProgrammer extends Programmer {
Обратите внимание, что при наследовании представления предка необходимо указать префикс super перед именем представления в конструкции extendsView. В нашем примере представление WithSections класса JavaProgrammer будет отображать все свойства из представления WithSections класса Programmer плюс свои свойства, указанные в members.
Посмотрим на представление WithSections класса JavaProgrammer:
view_en096.jpg

Наследование безыменного представления по-умолчанию

Чтобы наследовать безыменное представление (которое является представлением по-умолчанию для класс), нужно указать имя DEFAULT в конструкции for extendsView. Рассмотрим на примере:
@Views({
    @View(members="name, sex; mainLanguage, favouriteFramework; experiences"),
    @View(name="Complete", extendsView="DEFAULT",
        members = "frameworks"
    )
})
Представление Complete будет иметь все свойства безыменного представления (name, sex, mainLanguage, favouriteFramework, experiences) плюс свойство frameworks.
Наследование представлений работает только в отношении свойств и их компоновки. Действия, события и другие переопределения на уровне свойств не наследуются.

Про группы и секции (философия)

Необходимо обратить внимание на то, что вместо вкладок (tabs) в OX мы используем термин "секции" (sections), а вместо рамок (frames) используем "группы" (groups). Дело в том, что OpenXava пытается находиться на высоком уровне абстракции, не привязанном к конкретной реализации визуализации. В OX группа - это набор свойств-членов, которые связаны семантически. А секция позволяет разбить данные на несколько частей. Что в свою очередь полезно, когда мы имеем дело с большим количеством данных, которые не могут быть отображены одновременно. Тот факт, что группы отображаются как рамки (frames), а секции как вкладки - (с точки зрения OpenXava) лишь особенность текущей реализации визуализации. Возможно в будущем OpenXava будет отображать секции как деревья или как то еще...кто знает.

Введение в аннотации свойств

Мы можем изменить стиль отображения и поведение визуальных элементов пользовательского интерфейса (виджетов) для свойств с помощью аннотаций. Более того, можно сделать так, чтобы данные изменения были задействованы только для конкретных представлений.
Рассмотрим на примере. Начнем с того, что мы определим модель с несколькими представлениями:
@Entity
@Views({
    @View( members= "number; type; name; address" ),
    @View( name="A", members= "number; type; name; address; seller" ),
    @View( name="B", members= "number; type; name; seller; alternateSeller" ),
    @View( name="C", members="number; type; name; address; deliveryPlaces" )
})
public class Customer {
 
Теперь мы хотим сделать виджет для свойства name доступным только для чтения (read only). Используем аннотацию @ReadOnly:
@ReadOnly
private String name;
Теперь свойство name будет доступным только для чтения во всех представлениях.
Но довольно часто нужно запретить редактирование значения свойства только для некоторых представлений. В нашем примере свойство name будет не редактируемым для представлений B и C:
@ReadOnly(forViews="B, C")
private String name;
Можно добиться того же, действуя от обратного, указав представления, для которых данное свойство не будет "read only" (только для чтения). Сделать это можно, используя конструкцию notForViews:
@ReadOnly(notForViews="DEFAULT, A")
private String name;
В нашем примере с помощью notForViews мы указываем, что свойство name будет не редактируемым для всех представлений, кроме DEFAULT (напомню, что это безыменное представление, задействованное по-умолчанию).

Некоторые аннотации имеют гибкие настройки, что достигается заданием параметров аннотаций. Например, для указания представления, которое будет использовано для отображения ссылки, используется аннотация @ReferenceView:
@ReferenceView("Simple")
private Seller seller;
В этом примере объект seller отображается как представление Simple, определенное в классе Seller.

Более сложный пример. Предположим, что мы хотим использовать представление Simple модели Seller только в nредставлении B модели Customer. Это просто:
@ReferenceView(forViews="B", value="Simple")
private Seller seller;
Рассмотрим более сложный пример. Что будет, если мы хотим использовать представление Simple модели Seller ТОЛЬКО в представлении B модели Customer, а представление VerySimple той же модели Seller в представлении A того же Customer? В этом случае мы можем использовать несколько конструкций @ReferenceView, сгруппировав их с помощью @ReferenceViews. Рассмотрим код примера:
@ReferenceViews({
    @ReferenceView(forViews="B", value="Simple"),
    @ReferenceView(forViews="A", value="VerySimple")
})
 
Эти правила применимы для всех аннотаций в данной главе, за исключением @View и @Views.

Свойства (Properties)

Перечислим все аннотации, применимые к простым свойствам и влияющие на пользовательский интерфейс OX:
@ReadOnly                   // 1
@LabelFormat                // 2
@DisplaySize                // 3
@OnChange                   // 4
@Action                     // 5
@Editor                     // 6
private type propertyName;
 
Все указанные аннотации соответствуют правилам для аннотаций представлений и все они не обязательны.
  1. @ReadOnly (OX): Не редактируемое свойство. Указание данной аннотации запрещает редактирование свойства (выбор или изменение его значения) конечным пользователем. Альтернативой использованию данной аннотации является программное использование возможностей org.openxava.view.View.
  2. @LabelFormat (OX): Определяет формат отображения текстовой метки свойства. Значение выбирается из следюущих: LabelFormatType.NORMAL, LabelFormatType.SMALL или LabelFormatType.NO_LABEL.
  3. @DisplaySize (OX): Определяет размер (в символах) поля редактирования свойства при отображении в UI OX. Если размер, заданный в @DisplaySize, меньше реального размера свойства, то UI OX позволяет ввести данные до реального размера . Если @DisplaySize не определен, то используется размер (в символах) свойства.
  4. @OnChange (OX): Действие (action), которое будет выполнено при изменении значения свойства. Обратите внимание, что в представлении возможно определить только одно действие @OnChange.
  5. @Action (OX): Действия (actions) (в пользовательском интерфейсе представляются как ссылки, кнопки или графические ссылки), визуально ассоциированные с данным свойством. Конечный пользователь будет использовать данные ссылки, чтобы вызывать соответствующие действия. OX позволяет определить несколько @Action для каждого представления.
  6. @Editor (OX): Название редактора (editor), который будет использован для отображения / редактирования данного свойства. Редактор должен быть определен в OpenXava/xava/default-editors.xml или xava/editors.xml вашего проекта.

Форматирование Текстовой Метки

Простейший пример использования аннотации @LabelFormat:
@LabelFormat(LabelFormatType.SMALL)
private int zipCode;
 
В этом примере zip code отображается как:
view_en100.jpg
Формат LabelFormatType.NORMAL является стилем по-умолчанию (метка отображается слева от значения свойства). Формат LabelFormatType.NO_LABEL позволяет не отображать метку.

Событие изменения значения Свойства (Property value change event)

Если мы хотим реализовать свою логику, вызываемую при возникновении событие изменения значения свойства, то нам необходимо использовать аннотацию @OnChange для указания класса, в котором будет описана наша логика:
@OnChange(OnChangeCustomerNameAction.class)
private String name;
 
Пример класса:
package org.openxava.test.actions;
 
import org.openxava.actions.*;
import org.openxava.test.model.*;
 
/**
 * @author Javier Paniza
 */
public class OnChangeCustomerNameAction extends OnChangePropertyBaseAction {  // 1
 
    public void execute() throws Exception {
        String value = (String) getNewValue();                                // 2
        if (value == null) return;
        if (value.startsWith("Javi")) {
            getView().setValue("type", Customer.Type.STEADY);                 // 3
        }
    }
 
}
Класс действия должен реализовывать интерфейс IOnChangePropertyAction. Но мы рекомендуем использовать наследовать OnChangePropertyBaseAction (1), который предоставляет дополнительные возможности. Используя его, вы можете использовать вызов getNewValue() (2) для получения "нового" значения, введенного пользователем. А также вызовом getView() (3) получать ссылку на View (что гибко менять поведение пользовательского интерфейса - переопределять занчения свойств, скрывать свойства, разрешать или запрещать редактирование и прочее).

Действия Свойства (Actions of property)

С помощью аннотации @Action мы можем определять действие, которое доступно пользователю для выполнения в UI и будет привязано к данному свойству:
@Action("Delivery.generateNumber")
private int number;
 
Обратите внимание, что в приведенном выше примере вместо того, чтобы указать класс-действие, мы указали идентификатор действия (action identifier). Идентификатор действия состоит из имени контроллера (controller name) и имени действия (the action name). Идентификатор действия должен быть зарегистрирован в controllers.xml. Пример регистрации действия generateNumber:
<controller name="Delivery">
    ...
    <action name="generateNumber" hidden="true"
        class="org.openxava.test.actions.GenerateDeliveryNumberAction">
        <use-object name="xava_view"/>  <!-- Not needed since v4m2 -->
    </action>
    ...
</controller>
 
Такие действия отображаются как ссылка или изображение справа за полем редактирования свойства. Пример:
view_en110.jpg
По-умолчанию ссылка-действие отображается только, когда свойство доступно для редактирования, но если свойство имеет аннотацию (@ReadOnly) или является вычисляемым, то ссылка-действия всегда отображается. Если необходимо, чтобы ссылка-действие отображалось в любом случае, то добиться этого можно, задав аттрибуи alwaysEnabled равным true. Пример:
@Action(value="Delivery.generateNumber", alwaysEnabled=true)
 
Аттрибут alwaysEnabled необязателен, значение по-умолчанию false.
Код класса-действия может выглядеть так:
package org.openxava.test.actions;
 
import org.openxava.actions.*;
 
/**
 * @author Javier Paniza
 */
public class GenerateDeliveryNumberAction extends ViewBaseAction {
 
    public void execute() throws Exception {
        getView().setValue("number", new Integer(77));
    }
 
}
Выше приведенный код не несет никакой полезной нагрузки, имеет только иллюстрационные цели. Впрочем, мы можем использовать любое Действие, определенное в controllers.xml. Логика вызова такого действия будет точно, такой же как для стандартных действий OpenXava. В главе 7 мы рассмотрим Действия более подробно.
Альтернативной возможностью создания такого Действия является реализация Действие интерфейса IPropertyAction. Но это возможно только для Действий, привязываемых к Свойствам (то есть задействованных с помощью @Action). В этом случае представление (контейнер данного Действия) и имя свойства будут переданы в Действие самой OpenXava. Упомянутый выше класс должен быть переработан следующим образом:
package org.openxava.test.actions;
 
import org.openxava.actions.*;
import org.openxava.view.*;
 
/**
 * @author Javier Paniza
 */
public class GenerateDeliveryNumberAction
    extends BaseAction
    implements IPropertyAction {                       // 1
    private View view;
    private String property;
 
    public void execute() throws Exception {
        view.setValue(property, new Integer(77));      // 2
    }
 
    public void setProperty(String property) {         // 3
        this.property = property;
    }
    public void setView(View view) {                   // 4
        this.view = view;
    }
 
}
Данное Действие реализует интерфейс IPropertyAction (1), что означает, что класс должен реализовать методы setProperty() (3) и setView() (4). Соответствующие значения будут вставлены системой OX в объект-Действие перед выполнением метода execute(), где. собственно, данные переменные могут быть использованы (2). В такой реализации нам нет необходимости указывать xava_view при определении действия controllers.xml. Представление вставляется OX вызовом setView() (4), сюда передается представление, которое содержит данное свойство. Т.е. если свойство агрегировано в представление, не являющее основным представлением модуля, то через SetView будет передано именно представление, в которое непосредственно входит свойство. Используя данную возможность, мы можем создавать действия, которые можно использовать многократно для разных представлений.

@Editor Выбор редактора для свойства

Редактор отображает свойство конечному пользователю и реализует редактирование значения свойства. OpenXava по-умолчанию использует редактор, ассоциированный со стереотипом или типом свойства. Используя аннотацию @Editor, можно задать нужный нам редактор, отличный от задействованного по-умолчанию.
Например, OpenXava использует combo-список для редактирования свойства, имеющего тип num. Но ничто не мешает нам переопределить такое поведение OX, задав отображение в виде Radio buttons:
@Editor(forViews="TypeWithRadioButton", value="ValidValuesRadioButton")
private Type type;
public enum Type { NORMAL, STEADY, SPECIAL };
 
В нашем примере для отображения / редактирования будет использован Редактор ValidValuesRadioButton, а не определённый по-умолчанию. Давайте посмотрим на определение ValidValueRadioButton в файле OpenXava/xava/default-editors.xml:
<editor name="ValidValuesRadioButton" url="radioButtonEditor.jsp"/>
Данный Редактор поставляется вместе с OpenXava. Также вы можете сами определять свои собственные Редакторы, используя разработанные вами страницы JSP, и подключать их, определяя их в xava/editors.xml вашего проекта.
Указывая @Editor, мы меняем Редактор только для одного свойства и некоторых Представлений (прим. переводчика - в исходном тексте близко к"Используя такой подход, мы меняем редактор для одного представления"). Если мы хотим создать Редактор, который можно использовать многократно для разных свойств, относящихся к одному типу Java или Стереотипу OX, или переопределить Редактор на уровне Приложения OX, то нам необходимо будет произвести настройки в конфигурационном файле xava/editors.xml.

Ссылки (Reference)

Для настройки отображения и поведения Ссылок можно воспользоваться следующей аннотации:
@ReferenceView     //  1
@ReadOnly          //  2
@NoFrame           //  3
@NoCreate          //  4
@NoModify          //  5
@NoSearch          //  6
@AsEmbedded        //  7
@SearchAction      //  8
@DescriptionsList  //  9
@LabelFormat       // 10
@Action            // 11
@OnChange          // 12
@OnChangeSearch    // 13
@Editor            // 14  New in v3.1.3
@ManyToOne
private type referenceName;
Все перечисленные аннотации следуют правилам для аннотаций Представлений и все они не обязательны (OpenXava подставит приемлемые для большинства случаев значения по-умолчанию, если аннотации не определены).
  1. @ReferenceView (OX): С помощью данной аннотации можно указать Представление, которое будет использоваться для отображения объекта, который представляет Ссылка. Если данная аннотация не определена, то использует Вид (представление) по-умолчанию данного объекта.
  2. @ReadOnly (OX): "Только для просмотра". Если указать данную аннотацию, конечный пользователь не сможет редактировать значение данного Свойства. Это более простая альтернатива управлению доступностью Свойства только на просмотр / редактирование путем программирования с использованием org.openxava.view.View.
  3. @NoFrame (OX): ("Без рамки") При указании данной аннотации, ссылка будет отображаться без рамки. По-умолчанию. Ссылка отображается в рамке.
  4. @NoCreate (OX): ("Не создавать") Указав данную аннотацию, вы можете запретить создание новых объектов, на которые указывает Ссылка. По-умолчанию, конечному пользователю разрешено создавать такие объекты.
  5. @NoModify (OX): ("Не изменять") Указав данную аннотацию, вы можете запретить редактирование текущего объекта, на который указывает Ссылка. По-умолчанию, такое редактирование разрешено.
  6. @NoSearch (OX): ("Без поиска") Указав данную аннотацию, вы можете запретить выполнение поиска и фильтрации для объектов по данной ссылке. По-умолчанию, поиск разрешен.
  7. @AsEmbedded (OX): ("Как встроенный" ) По-умолчанию, в OX разрешено редактирование данных для встроенного класса (embeddable class). В тоже время, редактирование данных Ссылочного объекта (Reference Entity) (ссылающегося на другую сущность, а не встроенный объект) в форме запрещено (пользователь лишь может выбрать среди существующих записей).Указав данную аннотацию, мы разрешаем создание и редактирование данных в Ссылочном объекте (т.е. имитировать поведение встроенного класса в OX). В случае ссылки на данные встроенного класса данная аннотация не дает никакого эффекта. Внимание! Обратите внимание, что ссылочные сущности не удаляются, если удаляется основная сущность, даже при указании @AsEmbedded.
  8. @SearchAction (OX): ("Действие-Поиск")Позволяет переопределить Действие "Поиск". Только одно Действие данного типа может быть определно для Представления.
  9. @DescriptionsList (OX): ("Список Описаний") Отображать данные как список описаний, обычно в виде combo-списка. Полезно в случае, ссылочный объект состоит из нескольких элементов.
  10. @LabelFormat (OX): ("Формат надписи") Формат отображения метки(надписи) для данной Ссылки. Данная аннотация может быть использована, только если данная Ссылка аннотирована @DescriptionsList. Работает также как и для Свойств.
  11. @Action (OX): ("Действие") Определяет Действия, доступные пользователю для данной Ссылки (отображаются как текстовые или графические веб-ссылки, кнопки). Такие действия ассоциированы (визуально) со Ссылкой и работают также как для обычного свойства. Для каждой Ссылки можно определить несколько Действие в Представлении.
  12. @OnChange (OX): ("При изменении") Действие, которое будет выполнено, когда значение Ссылки будет изменено.В пределах одного Представления может быть определено только одно Действие @OnChange.
  13. @OnChangeSearch (OX): ("При поиске по вводимым данным") Позволяет нам переопределить Действие, которое вызывается при поиске, который выполняется при вводе пользователем значения в поле Ссылки (на вводе каждого символа). В пределах одного Представления может быть определено только одно такое Действие.
  14. @Editor (OX): ("Редактор")(С версии v3.1.3) Идентификатор редактора, который будет использован для отображения Ссылки в данном Представлении. Редактор должен быть определен в одном из файлов OpenXava/xava/default-editors.xml или xava/editors.xml проекта.
Если мы не будем использовать выше перечисленные аннотации, то OpenXava отобразит Ссылку, используя Представление по-умолчанию.
Рассмотрим на примере:
@ManyToOne
private Family family;
Отображение будет следующим:
view_en120.jpg

Выбор представления (Choose view)

Одним из самых простых видоизменений интерфейса является указание заданного Представления для Модели. Делается это аннотацией @ReferenceView:
@ManyToOne(fetch=FetchType.LAZY)
@ReferenceView("Simple")
private Invoice invoice;
 
В модели Invoice должно быть определено Представление Simple:
@Entity
@Views({
    ...
    @View(name="Simple", members="year, number, date, yearDiscount;"),
    ...
})
public class Invoice {
Таким образом, вместо того, чтобы использовать Стандартное Представление объекта Invoice (которое показывает все данные счета), OpenXava будет показывать только следующее:
view_en130.jpg

Управление Рамкой

Если использовать @NoFrame вместе с группами, то визуально мы сможем сгруппировать Свойство, которое не является частью Ссылки, с этой Ссылкой. Пример:
@View( members=
    ...
    "seller [" +
    "    seller; " +
    "    relationWithSeller;" +
    "]" +
    ...
)
public class Customer {
    ...
    @ManyToOne(fetch=FetchType.LAZY)
    @NoFrame
    private Seller seller;
    ...
}
 
Посмотрим на результат:
view_en140.jpg

Действие при поиске (Custom search action)

В OX реализован "живой поиск" или "поиск на лету". Пользователь вашего приложения может искать новое значение для Ссылки, просто набирая на клавиатуре символы искомого значения. The final user can search a new value for the reference simply by keying the new code and leaving the editor the data of reference is obtained; for example, if the user keys "1" on the seller number field, then the name (and the other data) of the seller "1" will be automatically filled. Also the user can click in the lantern, in this case the user will go to a list where he can filter, order, etc, and mark the wished object.
Для создания своей поисковой логики вам нужно использовать аннотацию @SearchAction нижеследующим образом:
@ManyToOne(fetch=FetchType.LAZY) @SearchAction("MyReference.search")
private Seller seller;
 
When the user clicks in the lantern your action is executed, which must be defined in controllers.xml.
<controller name="MyReference">
    <action name="search" hidden="true"
        class="org.openxava.test.actions.MySearchAction"
        image="images/search.gif">
        <use-object name="xava_view"/>  <!-- Not needed since v4m2 -->
        <use-object name="xava_referenceSubview"/>  <!-- Not needed since v4m2 -->
        <use-object name="xava_tab"/>  <!-- Not needed since v4m2 -->
        <use-object name="xava_currentReferenceLabel"/>  <!-- Not needed since v4m2 -->
    </action>
    ...
</controller>
Вы сами можете переопределять логику MySearchAction. К примеру, можно переопределить Действие Стандартного Поиска, задав дополнительное условие фильтрации:
package org.openxava.test.actions;
 
import org.openxava.actions.*;
 
/**
 * @author Javier Paniza
 */
 
public class MySearchAction extends ReferenceSearchAction {
 
    public void execute() throws Exception {
        super.execute();    // The standard search behaviour
        getTab().setBaseCondition("${number} < 3"); // Adding a filter to the list
    }
 
}
Более подробную информацию о контроллерах вы найдете в Главе 7 "Контроллеры".

Действие при создании нового объекта (Custom creation action)

Если не использовать аннотацию @NoCreate, то пользователю отображается ссылка на создание нового объекта. По-умолчанию, когда пользователь нажимает на данную ссылку, отображается вид по-умолчанию ссылочного объекта. В котором пользователь может вводить данные и по ссылке создать соответствующий объект. Если вы хотите в Форму свои добавить новые реализованные вами Действия (не забудьте создать их до этого), то ..If you want to define your custom actions (among them your create custom action) in the form used when creating a new object, you must have a controller named as component but with the suffix Creation. If OpenXava see this controller it uses it instead of the default one to allow creating a new object from a reference. For example, you can write in your controllers.xml:
<!--
Because its name is WarehouseCreation (model name + Creation) it is used
by default for create from reference, instead of NewCreation.
Action 'new' is executed automatically.
-->
<controller name="WarehouseCreation">
    <extends controller="NewCreation"/>
    <action name="new" hidden="true"
        class="org.openxava.test.actions.CreateNewWarehouseFromReferenceAction">
        <use-object name="xava_view"/>  <!-- Not needed since v4m2 -->
    </action>
</controller>
In this case when the user clicks on the 'create' link, the user is directed to the default view of Warehouse and the actions in WarehouseCreation will be allowed.
If you have an action called 'new', it will be executed automatically before all. It can be used to initialize the view used to create a new object.

Действие пи модификации (Custom modification action)

Если вы не использовали аннотацию @NoModify при определении вашего Entity, то пользователю доступна ссылка на редактирование текущего объекта. По-умолчанию, когда пользователь нажимает на ссылку, отображается безымянный Вид (Преставление) по-умолчанию, в котором пользователь может менять значения свойств Entity и сохранить эти изменения, навав на соответсвующую кнопку. Если вы хотите создать свои собственные Действия (among them your update custom action) in the form used when modifying the current object, you must have a controller named as component but with the suffix Modification. If OpenXava see this controller it uses it instead of the default one to allow modifying the current object from a reference. For example, you can write in your controllers.xml:
<!--
Because its name is WarehouseModification (model name + Modification) it is used
by default for modifying from reference, instead of Modification.
The action 'search' is executed automatically.
-->
<controller name="WarehouseModification">
    <extends controller="Modification"/>
    <action name="search" hidden="true"
        class="org.openxava.test.actions.ModifyWarehouseFromReferenceAction">
        <use-object name="xava_view"/>  <!-- Not needed since v4m2 -->
    </action>
</controller>
In this case when the user clicks on the 'modify' link, the user is directed to the default view of Warehouse and the actions in WarehouseModification will be allowed.
If you have an action called 'search', it will be executed automatically before all. It is used to initialize the view with the object to modify.

Список описания (Descriptions list (combos))

With @DescriptionsList you can instruct OpenXava to visualize references as a descriptions list (actually a combo). This can be useful, if there are only a few elements and these elements have a significant name or description.
The syntax is:
@DescriptionsList(
    descriptionProperties="properties",  // 1
    depends="depends",                   // 2
    condition="condition",               // 3
    orderByKey="true|false",             // 4
    order="order"                        // 5
)
  1. descriptionProperties (optional): The property or properties to show in the list, if not specified, the property named description, descripcion, name or nombre is assumed. If the referenced object does not have a property called this way then it is required to specify a property name here. It allows to set more than one property separated by commas. To the final user the values are concatenated.
  2. depends (optional): It's used in together with condition. It can be achieve that the list content depends on another value displayed in the main view (if you simply type the name of the member) or in the same view (if you type this. before the name of the member).
  3. condition (optional): Allows to specify a condition (with SQL style) to filter the values that are shown in the description list.
  4. orderByKey (optional): By default the data is ordered by description, but if you set this property to true it will be ordered by key.
  5. order (optional): Allows to specify an order (with SQL style) for the values that are shown in the description list.
The simplest usage is:
@ManyToOne(fetch=FetchType.LAZY)
@DescriptionsList
private Warehouse warehouse;
 
That displays a reference to warehouse in this way:
view_en150.jpg
In this case it shows all warehouses, although in reality it uses the baseCondition and the filter specified in the default @Tab of Warehouse. You will see more about tabs in chapter 5.
If you want, for example, to display a combo with the product families and when the user chooses a family, then another combo will be filled with the subfamilies of the chosen family. An implementation can look like this:
@ManyToOne(fetch=FetchType.LAZY)
@DescriptionsList(orderByKey=true)            // 1
private Family family;
 
@ManyToOne(fetch=FetchType.LAZY) @NoCreate    // 2
@DescriptionsList(
    descriptionProperties="description",      // 3
    depends="family",                         // 4
    condition="${family.number} = ?"          // 5
    order="${description} desc"               // 6
 )
private Subfamily subfamily;
 
Two combos are displayed one with all families loaded and the other one empty. When the user chooses a family, then the second combo is filled with all its subfamilies.
In the case of Family the property description of Family is shown, since the default property to show is 'description' or 'name'. The data is ordered by key and not by description (1). In the case of Subfamily (2) the link to create a new subfamily is not shown and the property to display is 'description' (in this case this maybe omitted).
With depends (4) you make that this combo depends on the reference family, when change family in the user interface, this descriptions list is filled applying the condition condition (5) and sending as argument (to set value to ?) the new family value. And the entries are ordered descending by description (6).
In condition and order you put the property name inside a ${} and the arguments as ?. The comparator operators are the SQL operators.
New in v3.0.3: Qualified properties are allowed for condition and order even if they are not in descriptionProperties. That is, you can write a condition like this:
@DescriptionsList( descriptionProperties="name",
    condition="${family.level.description} = 'TOP'"
)
private Subfamily subfamily;
You can specify several properties to be shown as description:
@ManyToOne(fetch=FetchType.LAZY)
@ReadOnly
@DescriptionsList(descriptionProperties="level.description, name")
private Seller alternateSeller;
 
In this case the concatenation of the description of level and the name is shown in the combo. Also you can see how it is possible to use qualified properties (level.description).
If you use @ReadOnly in a reference annotated as @DescriptionsList, then the description (in this case level.description + name) is displayed as a simple text property instead of using a combo.

Событие изменения значения Ссылки (Reference value change event)

If you wish to react to the event of a value change of a reference you can write:
@ManyToOne(fetch=FetchType.LAZY)
@OnChange(OnChangeCarrierInDeliveryAction.class)
private Carrier carrier;
 
In this case your action listens to the change of carrier number.
The code to execute is:
package org.openxava.test.actions;
 
import org.openxava.actions.*;
 
/**
 * @author Javier Paniza
 */
public class OnChangeCarrierInDeliveryAction
    extends OnChangePropertyBaseAction {                                        // 1
 
    public void execute() throws Exception {
        if (getNewValue() == null) return;
        getView().setValue("remarks", "The carrier is " + getNewValue());
        addMessage("carrier_changed");
    }
 
}
The action implements IOnChangePropertyAction, by means of OnChangePropertyBaseAction (1), although it's a reference. We receive the change of the key property of the reference; in this case carrier.number. The rest is as in the property case.

Поиск Ссылки при событии Change(Reference search on change event)

The user can search the value of a reference simply typing its key. For example, if there is a reference to Subfamily, the user can type the subfamily number and automatically the subfamily data is loaded in the view. This is done using a default on change action that does the search. You can specify your own action for search when key change using @OnChangeSearch annotation, just in this way:
@ManyToOne(fetch=FetchType.LAZY)
@OnChangeSearch(OnChangeSubfamilySearchAction.class)
private Subfamily subfamily;
This action is executed for doing the search, instead of the standard action, when the user changes the subfamily number.
The code to execute is:
package org.openxava.test.actions;
 
import org.openxava.actions.*;
 
/**
 *
 * @author Javier Paniza
 */
 
public class OnChangeSubfamilySearchAction
    extends OnChangeSearchAction {  // 1
 
    public void execute() throws Exception {
        if (getView().getValueInt("number") == 0) {
            getView().setValue("number", new Integer("1"));
        }
        super.execute();
    }
 
}
 
The action implements IOnChangePropertyAction, by means of OnChangeSearchAction (1), although it's a reference. It receives the change of the key property of the reference; in this case subfamily.number.
This case is an example of refining the behaviour of on change search, because it extends from OnChangeSearchAction, that is the default action for searching, and calls to super.execute(). Also it's possible to do a regular on change action (extending from OnChangePropertyBaseAction for example) overriding completely the search logic.

Назначение Редакторов (Choosing an editor (reference, с v3.1.3))

An editor display the reference to the user and allows him to edit its value. By default, the editor OpenXava uses for references is a detailed view inside a frame (the standard way) or a combo (if you use @DescriptionsList), but you can specify your own editor to display a concrete reference using @Editor.
For example, if you have a reference to a Color entity and you want to display it in some particular entity or view using a custom user interface, such as a radio buttons group with available colors. Just can do it in this way:
@ManyToOne(fetch=FetchType.LAZY)
@Editor("ColorRadioButtons")
private Color color;
In this case the ColorRadioButtons editor will be used for displaying/editing, instead of the default one. You must define your ColorRadioButton editor in the xava/editors.xml file of your project:
<editor name="ColorRadioButtons" url="colorRadioButtonsEditor.jsp"/>
Also you have to write the JSP code for your editor in colorRadioButonsEditor.jsp.
This feature is for changing the editor for a concrete reference in a concrete entity only, or even only in one view of that entity (using @Editor(forViews=)). If you want to change the editor for all references to certain entity type at application level then it's better to configure it using xava/editors.xml file.
Learn more on Editors for references section of chapter 9.

Коллекции (Collections)

You can refine the visual aspect and behavior of a collection in a view using the next annotations:
@CollectionView         //  1
@ReadOnly               //  2
@EditOnly               //  3
@NoCreate               //  4
@NoModify               //  5
@AsEmbedded             //  6
@ListProperties         //  7
@RowStyle               //  8
@EditAction             //  9
@ViewAction             // 10
@NewAction              // 11
@SaveAction             // 12
@HideDetailAction       // 13
@RemoveAction           // 14
@RemoveSelectedAction   // 15
@ListAction             // 16
@DetailAction           // 17
@OnSelectElementAction  // 18  New in v3.1.2
@Editor                 // 19  New in v3.1.3
@OneToMany/@ManyToMany
private Collection collectionName;
All these annotations follow the rules for view annotations and all they are optionals. OpenXava always assumes a correct default values if they are omitted.
  1. @CollectionView (OX): The view of the referenced object (each collection element) which is used to display the detail. By default the default view is used.
  2. @ReadOnly (OX): If you set it then the final user only can view collection elements, he cannot add, delete or modify elements.
  3. @EditOnly (OX): If you set it then the final user can modify existing elements, but not add or remove collection elements.
  4. @NoCreate (OX): If you set it then the final user doesn't get the link to create new objects of the referenced object type. It does not apply to embedded collections.
  5. @NoModify (OX): If you set it then the final user doesn't get the link to modify the objects of the referenced object type. It does not apply to embedded collections.
  6. @AsEmbedded (OX): By default the embedded collections (with cascade type REMOVE or ALL) allow the users to create and to edit elements, while the regular collections allow only to choose existing entities to add to (or remove from) the collection. If you put @AsEmbedded then the collection behaves as a embedded collection even though it hasn't cascade type REMOVE or ALL, allowing to the user to add objects and editing them directly. It has no effect in case of embedded collections.
  7. @ListProperties (OX): Properties to show in the list for visualization of the collection. You can qualify the properties. By default it shows all persistent properties of the referenced object (excluding references and calculated properties).
  8. @RowStyle (OX): To give a special style to some rows. Behaves equals that in the Tab case. It does not works for calculated collections. It's possible to define several @RowStyle for each view.
  9. @EditAction (OX): Allows you to define your custom action to begin the editing of a collection element. This is the action showed in each row of the collection, if the collection is editable. Only one @EditAction per view is allowed.
  10. @ViewAction (OX): Allows you to define your custom action to view a collection element. This is the action showed in each row, if the collection is read only. Only one @ViewAction per view is allowed.
  11. @NewAction (OX): Allows you to define your custom action to start adding a new element to the collection. This is the action executed on click in 'Add' link. Only one @ViewAction per view is allowed.
  12. @SaveAction (OX): Allows you to define your custom action to save the collection element. This is the action executed on click in 'Save detail' link. Only one @SaveAction per view is allowed.
  13. @HideDetailAction (OX): Allows you to define your custom action to hide the detail view. This is the action executed on click in 'Close' link. Only one @HideDetailAction per view is allowed.
  14. @RemoveAction (OX): Allows you to define your custom action to remove the element from the collection. This is the action executed on click in 'Remove detail' link. Only one @RemoveAction per view is allowed.
  15. @RemoveSelectedAction (OX): Allows you to define your custom action to remove the selected elements from the collection. This is the action executed when a user select some rows and then click in 'Remove selected' link. Only one @RemoveSelectedAction per view is allowed.
  16. @ListAction (OX): To add actions in list mode; usually actions which scope is the entire collection. It's possible to define several @ListAction for each view.
  17. @DetailAction (OX): To add actions in detail mode, usually actions which scope is the detail that is being edited. It's possible to define several @DetailAction for each view.
  18. @OnSelectElementAction (OX): (New in v3.1.2) To define an action to be executed when an element of the collection is selected or unselected. It's possible to define several @OnSelectElementAction for each view.
  19. @Editor (OX): (New in v3.1.3) Name of the editor to use for displaying the collection in this view. The editor must be declared in OpenXava/xava/default-editors.xml or xava/editors.xml of your project.
If you do not use any of these annotations then the collection is displayed using the persistent properties in list mode and the default view to represent the detail; although in typical scenarios the properties of the list and the view for detail are specified:
@CollectionView("Simple"),
@ListProperties("number, name, remarks, relationWithSeller, seller.level.description, type")
@OneToMany(mappedBy="seller")
private Collection<Customer> customers;
And the collection is displayed:
view_en160.jpgYou see how you can put qualified properties into the properties list (as seller.level.description).
When the user clicks on edit.gif ('Edit'), then the view Simple of Customer will be rendered; for this you must have defined a view called Simple in the Customer entity (the model of the collection elements).
This view is also used if the user click on create_new.gif ('Add') in an embedded collection, otherwise OpenXava does not show this view, instead it shown a list of entities to add.
If the view Simple of Customer is like this:
@View(name="Simple", members="number; type; name; address")
On clicking in a detail the following will be shown:
view_en170.jpg

Custom edit/view action

You can refine easily the behavior when the edit.gif ('Edit') link is clicked using @EditAction:
@EditAction("Invoice.editDetail")
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
private Collection<InvoiceDetail> details;
 
You have to define Invoices.editDetail in controllers.xml:
<controller name="Invoice">
    ...
    <action name="editDetail" hidden="true"
        image="images/edit.gif"
        class="org.openxava.test.actions.EditInvoiceDetailAction">
        <use-object name="xava_view"/>  <!-- Not needed since v4m2 -->
    </action>
    ...
</controller>
Посмотрим на программный код класса-действия:
package org.openxava.test.actions;
 
import java.text.*;
 
import org.openxava.actions.*;
 
/**
 * @author Javier Paniza
 */
public class EditInvoiceDetailAction extends EditElementInCollectionAction {       // 1
 
    public void execute() throws Exception {
        super.execute();
        DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
        getCollectionElementView().setValue(                               // 2
            "remarks", "Edit at " + df.format(new java.util.Date()));
    }
 
}
In this case you only refine hence your action extends (1) EditElementInCollectionAction. In this case you only specify a default value for the remarks property. Note that to access the view that displays the detail you can use the method getCollectionElementView() (2).
Also it's possible to remove the edit action from the User Interface, in this way:
@EditAction("")
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
private Collection<InvoiceDetail> details;
You only need to put an empty string as value for the action. Although in most case it's enough to define the collection as @ReadOnly.
The technique to refine the view action (the action for each row, if the collection is read only) is the same but using @ViewAction instead of @EditAction.

Custom list actions

Adding our custom list actions (actions that apply to entire collections) is easy using @ListAction:
@ListAction("Carrier.translateName")
private Collection<Carrier> fellowCarriers;
Now a new link is shown to the user:
view_en180.jpg
Не забудьте оперелить действие в controllers.xml:
<controller name="Carrier">
    ...
    <action name="translateName" hidden="true"
        class="org.openxava.test.actions.TranslateCarrierNameAction">
    </action>
    ...
</controller>
 
Программный код класса-действия:
package org.openxava.test.actions;
 
import java.util.*;
import org.openxava.actions.*;
import org.openxava.test.model.*;
 
/**
 * @author Javier Paniza
 */
public class TranslateCarrierNameAction extends CollectionBaseAction {  // 1
 
    public void execute() throws Exception {
        Iterator it = getSelectedObjects().iterator();                  // 2
        while (it.hasNext()) {
            Carrier carrier = (Carrier) it.next();
            carrier.translate();
        }
    }
 
}
 
The action extends CollectionBaseAction (1), this way you can use methods as getSelectedObjects() (2) that returns a collection with the objects selected by the user. There are others useful methods, as getObjects() (all elements collection), getMapValues() (the collection values in map format) and getMapsSelectedValues() (the selected elements in map format).
As in the case of detail actions (see next section) you can use getCollectionElementView().
Also it's possible to use actions for list mode as list actions for a collection.

Default list actions

If you want to add some custom list actions to all the collection of your application you can do it creating a controller called DefaultListActionsForCollections in your own xava/controllers.xml file as following:
<controller name="DefaultListActionsForCollections">
    <extends controller="Print"/>
    <action name="exportAsXML"
        class="org.openxava.test.actions.ExportAsXMLAction">
    </action>
</controller>
In this way all the collections will have the actions of Print controller (for export to Excel and generate PDF report) and your own ExportAsXMLAction. This has the same effect of @ListAction (look at custom list actions section) but it applies to all collections at once.
This feature does not apply to calculated collections.

Custom detail actions

Also you can add your custom actions to the detail view used for editing each element. This is accomplish by means of @DetailAction annotation. These actions are applicable only to one element of collection. For example:
@DetailAction("Invoice.viewProduct")
@OneToMany (mappedBy="invoice", cascade=CascadeType.REMOVE)
private Collection<InvoiceDetail> details;
In this way the user has another link to click in the detail of the collection element:
view_en190.jpg
Также необходимо определить действие в controllers.xml:
<controller name="Invoice">
    ...
    <action name="viewProduct" hidden="true"
        class="org.openxava.test.actions.ViewProductFromInvoiceDetailAction">
        <use-object name="xava_view"/>  <!-- Not needed since v4m2 -->
        <use-object name="xavatest_invoiceValues"/>  <!-- Not needed since v4m2 -->
    </action>
    ...
</controller>
Программный код действия:
package org.openxava.test.actions;
 
import java.util.*;
import javax.ejb.*;
 
import org.openxava.actions.*;
 
/**
 * @author Javier Paniza
 */
public class ViewProductFromInvoiceDetailAction
    extends CollectionElementViewBaseAction                 // 1
    implements INavigationAction {
 
    @Inject  // Since v4m2
    private Map invoiceValues;
 
    public void execute() throws Exception {
        try {
            setInvoiceValues(getView().getValues());
            Object number =
                getCollectionElementView().getValue("product.number"); // 2
            Map key = new HashMap();
            key.put("number", number);
            getView().setModelName("Product");      // 3
            getView().setValues(key);
            getView().findObject();
            getView().setKeyEditable(false);
            getView().setEditable(false);
        }
        catch (ObjectNotFoundException ex) {
            getView().clear();
            addError("object_not_found");
        }
        catch (Exception ex) {
            ex.printStackTrace();
            addError("system_error");
        }
    }
 
    public String[] getNextControllers() {
        return new String [] { "ProductFromInvoice" };
    }
 
    public String getCustomView() {
        return SAME_VIEW;
    }
 
    public Map getInvoiceValues() {
        return invoiceValues;
    }
 
    public void setInvoiceValues(Map map) {
        invoiceValues = map;
    }
 
}
You can see that it extends CollectionElementViewBaseAction (1) thus it has available the view that displays the current element using getCollectionElementView() (2). Also you can get access to the main view using getView() (3). In chapter 7 you will see more details about writing actions.
Also, using the view returned by getCollectionElementView() you can add and remove programmatically detail and list actions with addDetailAction(), removeDetailAction(), addListAction() and removeListAction(), see API doc for org.openxava.view.View.

Переопределение поведения коллекции по-умолчанию

Using @NewAction, @SaveAction, @HideDetailAction, @RemoveAction and @RemoveSelectedAction you can refine the default behavior of collection view. For example if you want to refine the behavior of save a detail action you can define your view in this way:
@SaveAction("DeliveryDetail.save")
@OneToMany (mappedBy="delivery", cascade=CascadeType.REMOVE)
private Collection<DeliveryDetail> details;
 
Не забудьте - вам нужно определить действие DeliveryDetails.save в файле описания контроллеров controllers.xml:
<controller name="DeliveryDetail">
    ...
    <action name="save"
        class="org.openxava.test.actions.SaveDeliveryDetailAction">
        <use-object name="xava_view"/>  <!-- Not needed since v4m2 -->
    </action>
    ...
</controller>
И теперь создайте ваш класс, реализующий действие для сохранения:
package org.openxava.test.actions;
 
import org.openxava.actions.*;
 
/**
 * @author Javier Paniza
 */
 
public class SaveDeliveryDetailAction extends SaveElementInCollectionAction { // 1
 
    public void execute() throws Exception {
        super.execute();
        // Here your own code                                                 // 2
    }
 
}
Более общим случаем является расширение штатного (default) поведения..., for that you have to extend the original class for saving a collection detail (1), that is SaveElementInCollection action, then call to super from execute() method (2), and after it, writing your own code.
Also it's possible to remove any of these actions from User Interface, for example, you can define a collection in this way:
@RemoveSelectedAction("")
@OneToMany (mappedBy="delivery", cascade=CascadeType.REMOVE)
private Collection<DeliveryDetail> details;
In this case the action for removing the selected elements in the collection will be missing in the User Interface. As you see, only it's needed to declare an empty string as the name of the action.

Действие над выбранным элементов коллекции (Action when a collection element is selected (с версии v3.1.2))

Вы можете опеределить Действие, которое будет выполнено в момент, когда элемент коллекции будет выбран или наоборот (unselected). Это реализуется с помощью аннотации @OnSelectElementAction annotation. For example, imagine that you have a collection just like this one:
view_en195.jpg
And you want that when a row is selected the value of the selectedIngredientSize field in the user interface will be updated. Let's code it.
First you have to annotate your collection:
@OnSelectElementAction("Formula.onSelectIngredient") // 1
@OneToMany(mappedBy="formula", cascade=CascadeType.REMOVE)
private Collection<FormulaIngredient> ingredients;
 
In this simple way (1), and thanks to the @OnSelectElementAction annotation, you say that when the user clicks on the checkbox of the collection row the Formula.onSelectIngredient action will be executed. This action is declared in controllers.xml, in this way:
<controller name="Formula">
    ...
    <action name="onSelectIngredient" hidden="true"
        class="org.openxava.test.actions.OnSelectIngredientAction">
        <use-object name="xava_view"/>  <!-- Not needed since v4m2 -->
    </action>
    ...
</controller>
 
Now, only remains the code of the OnSelectIngredientAction class:
public class OnSelectIngredientAction extends OnSelectElementBaseAction {  // 1
 
    public void execute() throws Exception {
        int size = getView().getValueInt("selectedIngredientSize");
        size = isSelected() ? size + 1 : size - 1;                         // 2
        getView().setValue("selectedIngredientSize", new Integer(size));
    }
 
}
Наиболее простой способ реализации такого действия - это насследоваться от OnSelectElementBaseAction, что позволит вам получить доступ к Выбранному Свойству (путем обращения к isSelected(), 2) that indicates wheter the user has selected or unselected the row; and row (using getRow()) that indicates the row number of the affected collection element.

Выбор редактора (коллекция, с версии v3.1.3)

Редактор, отображающий Коллекцию, позволяет конечному пользователю редактировать значения в коллекции. By default, the editor OpenXava uses for collections is a list that shows the data in a tabular fashion, allowing to order, filter and paging, but you can specify your own editor to display a concrete collection using @Editor.
For example, if you have a collection of Customer entities and you want to display it in some particular entity or view using a custom user interface, such as a simple list of names. Just can do it in this way:
@OneToMany(mappedBy="seller")
@Editor("CustomersNames")
private Collection<Customer> customers;
In this case the CustomersNames editor will be used for displaying/editing, instead of the default one. You must define your CustomersNames editor in the xava/editors.xml file of your project:
<editor name="CustomersNames" url="customersNamesEditor.jsp"/>
Также вам необходимо создать свой редактор в виде customersNamesEditor.jsp.
Данный функционал позволяет изменить редактор для конкретной коллекции, использованой в конкретной Сущности. This feature is for changing the editor for a concrete collection in a concrete entity only, or even only in one view of that entity (using @Editor(forViews=)). If you want to change the editor for all collections to certain entity type at application level then it's better to configure it using xava/editors.xml file.
Learn more on Editors for collections section of chapter 9.

Временные свойства для реализации UI

С помощью аннотации @Transient (JPA) вы можете можете определить свойство, которое не сохраняется в базу данных, но которое должно быть видно пользователю. Такие свойства могут использоваться для предоставления пользователю UI controls для управления пользовательским интерфейсом.
Пример:
@Transient
@DefaultValueCalculator(value=EnumCalculator.class,
    properties={
        @PropertyValue(name="enumType", value="org.openxava.test.model.Delivery$DeliveredBy")
        @PropertyValue(name="value", value="CARRIER")
    }
)
@OnChange(OnChangeDeliveryByAction.class)
private DeliveredBy deliveredBy;
public enum DeliveredBy { EMPLOYEE, CARRIER }
 
Как вы уже наверное обратили внимание, синтаксис точно такой же, как при определении обычного Свойства; you can even use enum and @DefaultValueCalculator. After defining the property you can use it in the view as usual, for example with @OnChange or putting it as member of a view.

View actions

В дополнение к Действиями, ассоциируемыми со Свойствами, Ссылками или Коллекциями, мы можем также определять Действия без привязки к Свойствам в любом месте определения Представления. Для этого нужно просто указать квалифицированное имя, не забыв указать () после имени, например, вот так:
@View( members=
    "number;" +
    "type;" +
    "name, Customer.changeNameLabel();" +
    ...
 
Отображение будет следующим:
view_en200.jpg
В примере мы видим ссылку , которая предназначена для вызова Действия Customer.changeNameLabel.
Обратите внимание, что если Представление-контейнер действия отображается в режиме просмотра, то данное Действие не будет отображаться. Если нам необходимо, чтобы действия отображалось всегда, то то нам необходимо вместо () после имени Действия указать (ALWAYS):
@View( name="Simple", members=
    "number;" +
    "type;" +
    "name, Customer.changeNameLabel(ALWAYS);" +
    ...
 
Обычно Действия, определенные в инструментальной панели (bar), - это Действия многократного применения, подходящие для разных представолений. Но иногда нам нужно определить действие, специфичное только для данного представления. В этом случае вышеописанный способ определения действия позволит опрелделить такое дейтсвие.
Более подробно Действия рассматриваются в Главе 7.

Временный класс (Transient class): Только для создания представлений

Сейчас мы расскажем, как создавать UI, не ассоциированный с данными в БД. Концепция OpenXava не предоставляет возможности создавать Представления, если не опредена Модель. В обычно случае Модель - это JPA Entity. Но это не обязательное требование. Мы можем определить класс, не делая его JPA Entity (т.е. без указания аннотации @Entity). OpenXava позволяет использовать такие Модели и строить для них представления.
Подчеркнем еще раз, такая модель, назовём её Временным классом (Transient class), не ассоциирована с таблицой БД и обычно используется только для реализации пользовательского интерфейса.
Рассмотрим пример:
package org.openxava.test.model;
 
import javax.persistence.*;
 
import org.openxava.annotations.*;
 
/**
 * Example of an transient OpenXava model class (not persistent). <p>
 *
 * This can be used, for example, to display a dialog,
 * or any other graphical interface.<p>
 *
 * Note that is not marked as @Entity <br>
 *
 * @author Javier Paniza
 */
 
@Views({
    @View(name="Family1", members="subfamily"),
    @View(name="Family2", members="subfamily"),
    @View(name="WithSubfamilyForm", members="subfamily"),
    @View(name="Range", members="subfamily; subfamilyTo")
})
public class FilterBySubfamily {
 
    @ManyToOne(fetch=FetchType.LAZY) @Required
    @NoCreate(forViews="Family1, Family2")
    @NoModify(forViews="Family2, WithSubfamilyForm")
    @NoSearch(forViews="WithSubfamilyForm")
    @DescriptionsLists({
        @DescriptionsList(forViews="Family1",
            condition="${family.number} = 1", order="${number} desc"
        ),
        @DescriptionsList(forViews="Family2",
            condition="${family.number} = 2"
        )
    })
    private Subfamily subfamily;
 
    @ManyToOne(fetch=FetchType.LAZY)
    private Subfamily subfamilyTo;
 
    public Subfamily getSubfamily() {
        return subfamily;
     }
 
    public void setSubfamily(Subfamily subfamily) {
        this.subfamily = subfamily;
    }
 
    public Subfamily getSubfamilyTo() {
        return subfamilyTo;
    }
 
    public void setSubfamilyTo(Subfamily subfamilyTo) {
        this.subfamilyTo = subfamilyTo;
    }
 
 
Главное отличие при определении Временного класса от обычной модели - это отсутствие аннотации @Entity , в остальном это обычный POJO класс Java. Естественно, что мы не можете использовать другие аннотации JPA в определении такого класса!
Таким путем мы можем создавать, например, диалоги для настройки параметров печат.
Временные классы могут использоваться только с модулями для представления "Карточка/Форма".
Обратите внимание, что временные классы должны быть определены в том же пакете, что JPA entities.
Используя данный функционал, вы можете использовать OpenXava как простой и гибкий генератор пользовательских интерфейсов, несмотря на то, что отображаемые данные не будут сохранены в БД.